﻿using System;
using System.Collections.Generic;
using System.Linq;

using Ewk.Math.Algebra;
using Ewk.Math.ComputationTypes;

namespace Ewk.MachineLearning.Clustering.MultidimensionalScaling
{
    public abstract class DifferenceContainer<T> : IDifferenceContainer<T>
        where T : struct, IComparable, IFormattable, IConvertible, IComparable<T>, IEquatable<T>
    {
        private readonly int _vectorCount;
        private readonly List<List<Computable<T>>> _differences = new List<List<Computable<T>>>();

        /// <summary>
        /// Initializes a new instance.
        /// </summary>
        /// <param name="calculationFunction">The function that calculates the difference.</param>
        /// <param name="vectors">The <see cref="Vector{T}"/> list to calculate differences for.</param>
        protected DifferenceContainer(Func<Vector<T>, Vector<T>, Computable<T>> calculationFunction,
                                      params Vector<T>[] vectors)
        {
            if (vectors == null) throw new ArgumentNullException("vectors");

            _vectorCount = vectors.Length;
            CalculateDifferences(vectors, calculationFunction);
        }

        /// <summary>
        /// Gets the number of <see cref="Vector{T}"/> instances in this container.
        /// </summary>
        public int VectorCount
        {
            get { return _vectorCount; }
        }

        /// <summary>
        /// Gets the difference between the i-th <see cref="Vector{T}"/>
        /// and the j-th <see cref="Vector{T}"/> of the internal <see cref="Vector{T}"/> list.
        /// </summary>
        /// <param name="i">The index of the first <see cref="Vector{T}"/> within the internal list.</param>
        /// <param name="j">The index of the second <see cref="Vector{T}"/> within the internal list.</param>
        /// <returns>A <see cref="Computable{T}"/> that represents the difference between the first and the second <see cref="Vector{T}"/>.</returns>
        public virtual Computable<T> this[int i, int j]
        {
            get
            {
                if (i < 0) throw new InvalidOperationException("Only positive values for indexes are valid.");
                if (j < 0) throw new InvalidOperationException("Only positive values for indexes are valid.");
                if (i >= _vectorCount) throw new InvalidOperationException("Only indexes smaller than the number of vectors are valid.");
                if (j >= _vectorCount) throw new InvalidOperationException("Only indexes smaller than the number of vectors are valid.");

                if (i == j)
                {
                    return Computable<T>.Factory.Zero;
                }

                return i < j
                           ? _differences.ElementAt(i).ElementAt(j - (i + 1))
                           : _differences.ElementAt(j).ElementAt(i - (j + 1));
            }
        }

        private void CalculateDifferences(IList<Vector<T>> vectors, Func<Vector<T>, Vector<T>, Computable<T>> calculationFunction)
        {
            for (var i = 0; i < vectors.Count - 1; i++)
            {
                var a = vectors[i];

                var differences = new List<Computable<T>>();
                _differences.Add(differences);

                for (var j = i + 1; j < vectors.Count; j++)
                {
                    var b = vectors[j];
                    differences.Add(calculationFunction(a, b));
                }
            }
        }
    }
}